// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

`include "prim_assert.sv"

module rom_ctrl
  import rom_ctrl_reg_pkg::NumAlerts;
  import prim_rom_pkg::rom_cfg_t;
#(
  parameter                       BootRomInitFile = "",
  parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
) (
  input  clk_i,
  input  rst_ni,

  // ROM configuration parameters
  input  rom_cfg_t rom_cfg_i,

  input  tlul_pkg::tl_h2d_t rom_tl_i,
  output tlul_pkg::tl_d2h_t rom_tl_o,

  input  tlul_pkg::tl_h2d_t regs_tl_i,
  output tlul_pkg::tl_d2h_t regs_tl_o,

  // Alerts
  input  prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,
  output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o
);

  import rom_ctrl_pkg::*;
  import rom_ctrl_reg_pkg::*;
  import prim_util_pkg::vbits;

  // TL interface ==============================================================

  tlul_pkg::tl_h2d_t tl_rom_h2d [1];
  tlul_pkg::tl_d2h_t tl_rom_d2h [1];

  logic  rom_reg_integrity_error;

  rom_ctrl_rom_reg_top u_rom_top (
      .clk_i      (clk_i),
      .rst_ni     (rst_ni),
      .tl_i       (rom_tl_i),
      .tl_o       (rom_tl_o),
      .tl_win_o   (tl_rom_h2d),
      .tl_win_i   (tl_rom_d2h),

      .intg_err_o (rom_reg_integrity_error),

      .devmode_i  (1'b1)
    );

  // The ROM ===================================================================

  // ROM_CTRL_ROM_SIZE is auto-generated by regtool and comes from the bus window size, measured in
  // bytes of content (i.e. 4 times the number of 32 bit words).
  localparam int unsigned RomSizeByte = ROM_CTRL_ROM_SIZE;
  localparam int unsigned RomSizeWords = RomSizeByte >> 2;
  localparam int unsigned RomIndexWidth = vbits(RomSizeWords);

  logic                     rom_req;
  logic [RomIndexWidth-1:0] rom_index;
  logic [39:0]              rom_rdata;
  logic                     rom_rvalid;

  logic                     rom_integrity_error;

  tlul_adapter_sram #(
    .SramAw(RomIndexWidth),
    .SramDw(32),
    .Outstanding(2),
    .ByteAccess(0),
    .ErrOnWrite(1),
    .EnableRspIntgGen(1),
    .EnableDataIntgGen(1) // TODO: Needs to be updated for integrity passthrough
  ) u_tl_adapter_rom (
    .clk_i        (clk_i),
    .rst_ni       (rst_ni),

    .tl_i         (tl_rom_h2d[0]),
    .tl_o         (tl_rom_d2h[0]),
    .en_ifetch_i  (tlul_pkg::InstrEn),
    .req_o        (rom_req),
    .req_type_o   (),
    .gnt_i        (1'b1),
    .we_o         (),
    .addr_o       (rom_index),
    .wdata_o      (),
    .wmask_o      (),
    .intg_error_o (rom_integrity_error),
    .rdata_i      (rom_rdata[31:0]),
    .rvalid_i     (rom_rvalid),
    .rerror_i     (2'b00)
  );

  prim_rom_adv #(
    .Width       (40),
    .Depth       (RomSizeWords),
    .MemInitFile (BootRomInitFile)
  ) u_rom
   (
    .clk_i    (clk_i),
    .rst_ni   (rst_ni),
    .req_i    (rom_req),
    .addr_i   (rom_index),
    .rdata_o  (rom_rdata),
    .rvalid_o (rom_rvalid),
    .cfg_i    (rom_cfg_i)
  );

  // TODO: The ROM has been expanded to 40 bits wide to allow us to add 9 ECC check bits. At the
  //       moment, however, we're actually generating the ECC data in u_tl_adapter_rom. That should
  //       go away soonish but, until then, waive the fact that we're not looking at the top bits of
  //       rom_rdata.
  logic unused_rom_rdata_top;
  assign unused_rom_rdata_top = &{1'b0, rom_rdata[39:32]};

  // Registers =================================================================

  rom_ctrl_regs_reg2hw_t reg2hw;
  rom_ctrl_regs_hw2reg_t hw2reg;
  logic                  reg_integrity_error;

  rom_ctrl_regs_reg_top u_reg_regs (
    .clk_i      (clk_i),
    .rst_ni     (rst_ni),
    .tl_i       (regs_tl_i),
    .tl_o       (regs_tl_o),
    .reg2hw     (reg2hw),
    .hw2reg     (hw2reg),
    .intg_err_o (reg_integrity_error),
    .devmode_i  (1'b1)
   );

  logic bus_integrity_error;
  assign bus_integrity_error = rom_reg_integrity_error | rom_integrity_error | reg_integrity_error;

  // FATAL_ALERT_CAUSE register
  assign hw2reg.fatal_alert_cause.integrity_error.d  = bus_integrity_error;
  assign hw2reg.fatal_alert_cause.integrity_error.de = bus_integrity_error;
  assign hw2reg.fatal_alert_cause.dummy.d  = 1'b0;
  assign hw2reg.fatal_alert_cause.dummy.de = 1'b0;

  // Alert generation ==========================================================

  logic [NumAlerts-1:0] alert_test;
  assign alert_test[AlertFatal] = reg2hw.alert_test.q &
                                  reg2hw.alert_test.qe;

  logic [NumAlerts-1:0] alerts;
  assign alerts[AlertFatal] = reg_integrity_error;

  for (genvar i = 0; i < NumAlerts; i++) begin: gen_alert_tx
    prim_alert_sender #(
      .AsyncOn(AlertAsyncOn[i]),
      .IsFatal(i == AlertFatal)
    ) u_alert_sender (
      .clk_i,
      .rst_ni,
      .alert_test_i  ( alert_test[i] ),
      .alert_req_i   ( alerts[i]     ),
      .alert_ack_o   (               ),
      .alert_state_o (               ),
      .alert_rx_i    ( alert_rx_i[i] ),
      .alert_tx_o    ( alert_tx_o[i] )
    );
  end

  // Asserts ===================================================================

  // All outputs should be known value after reset
  `ASSERT_KNOWN(RomTlODValidKnown_A, rom_tl_o.d_valid)
  `ASSERT_KNOWN(RomTlOAReadyKnown_A, rom_tl_o.a_ready)
  `ASSERT_KNOWN(RegTlODValidKnown_A, regs_tl_o.d_valid)
  `ASSERT_KNOWN(RegTlOAReadyKnown_A, regs_tl_o.a_ready)
  `ASSERT_KNOWN(AlertTxOKnown_A, alert_tx_o)

endmodule
